require 'rexml/document'

class XSDType
  attr_reader :name, :attrs, :elms
  
  def initialize(name)
    @name = name
    @attrs, @elms = [], []
  end
  def to_s
    str = ''
    str << "{TYPE\n\tname:\n\t\t#{@name}"
    str << "\n\tattributes:\n\t\t" unless @attrs.empty?
    @attrs.each { |attr|
      str << "#{attr}"
    }
    str << "\n\telements:\n\t\t" unless @elms.empty?
    @elms.each { |e|
      str << "#{e}"
    }
    str << "\n}"
  end
  def add_elem(elem)
    return if elem.nil? or !elem.kind_of?(XSDElement)
    @elms << elem
  end
  def add_attribute(attr)
    return if attr.nil? or !attr.kind_of?(XSDAttribute)
    @attrs << attr
  end
  def add_attributes(attr_list)
    attr_list.each { |attr| add_attribute(attr) }
  end
  def get_binding
    binding
  end
end

class XSDElement
  attr_reader :name, :type, :list
  def initialize(name, type, is_list=false)
    return if !type.kind_of?(XSDType)
    @name, @type = name, type
    @list = is_list || false
  end
  def to_s
    "{ELEMENT #{@name} -> #{@type.name}}"
  end
end

class XSDAttribute
  attr_reader :name, :type, :list
  def initialize(name, type)
    return if !type.kind_of?(XSDType)
    @name, @type = name, type
  end
  def to_s
      "{ATTR #{@name} -> #{@type.name}}"
  end
end

class Emitter  
  #attr_accessor :file  
  def initialize(file)
    @file = file
    @types = {}
  end  
  def emit
    #emit code
  end
  #Parse the schema file and return a map of the type name to the actual type
  #which is an instance of XSDType
  def parse
    open(@file, 'r') do |f|
      doc = REXML::Document.new(f)
      doc.elements.each("/schema/complexType") do |cte|
        type_name = get_non_nsaware_type(cte.attribute('name').value)
        type = get_or_create_cached_type(type_name)

        #start adding child elements
        cte.elements.each("./sequence/* | ./choice/* | ./any/*") do |cte_elem|
          #For each child of sequence/choice/any; it doesn't matter since we are
          #not validating the XML but generating classes from the XSD types.
          elem_name = cte_elem.attribute('name').value
          ns_type = get_non_nsaware_type(cte_elem.attribute('type').value)
          elem_type = get_or_create_cached_type(ns_type)
          is_list = true unless cte_elem.attribute('maxOccurs').nil?
          type.add_elem(XSDElement.new(elem_name, elem_type, is_list))
        end
                       
        cte.elements.each("./attribute") do |cte_attr|          
          #start adding attributes
          #since the type of an attr might be soemthing the parser has not yet
          #encountered, the type mgmt would eb done by the Emitter class. It would
          #return a dummy type if asked for one and when the real type definition
          #is encoutered would update the same copy.
          attr_name = cte_attr.attribute('name').value
          ns_type = get_non_nsaware_type(cte_attr.attribute('type').value)
          attr_type = get_or_create_cached_type(ns_type)
          type.add_attribute(XSDAttribute.new(attr_name, attr_type))                    
        end
      end
    end
    @types
  end
  def get_non_nsaware_type(type)
    #return the NS[namespace] stripped type name
    arr = type.split(':')
    arr.size > 1 ? arr[1] : type
  end
  def add_type(type)
    return if type.nil? or !type.kind_of?(XSDType)
    @types[type.name] = type
  end
  #Get the cached type and if not already present create a
  #new one.
  def get_or_create_cached_type(type)
    type_sym = type.to_sym
    type = @types[type_sym]
    add_type(type = XSDType.new(type_sym)) if type.nil?
    return type
  end
  def dump
    @types.each { |type| puts(type) }
  end
end

#ase = Emitter.new('./config/sbs.xsd')
#types = ase.parse
#types.each_value { |type|
#  puts type
#}

#TODO: Features to be implemented:
#Emitter for different languages based on rules and a new parameter passed to initializer
#Pluggable XML parsers
#Currently doesn't support anonymous complex/simple types for elements 